// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.file.d;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.httpfile.FileDownload;
import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.http.file.u.BigFileUploder;

/**
 * 通用大文件下载类（支持从指定断点处下载）。
 * 
 * @author JackJiang
 * @since 4.3
 */
//FIXME: 在更苛刻的安全性要求下，可考虑要求客户端把token字段带上来，以便进行更强的合法身份检查
public class BigFileDownloader extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet
{
	public static final String CONTENT_TYPE_DEFAULT = "application/octet-stream;charset=UTF-8";
	
	/**
	 * Servlet规范中本protected方法由public void service(,,)方法调用, 而public void service(,,)
	 * 则由Web容器调用(所以它是public公共方法),因此一般情况下尽量避免重写public void service(,,)方法
	 * ,而重写父类的本protedted方法则比较合理.
	 */
	protected void service(HttpServletRequest req, HttpServletResponse res)
		throws ServletException,IOException
	{
		// 此行代码解决网页端上传的中文文件名后，服务端读取到的是乱码的问题
		req.setCharacterEncoding("UTF-8"); 
				
		LoggerFactory.getLog().debug("[HTTP][大文件下载]下载大文件：你传过来的参数" +
				"user_uid="+req.getParameter("user_uid") // 读取图片的人（目前服务端保留，暂未使用）
				+",file_md5="+req.getParameter("file_md5")
				+",skipLength="+req.getParameter("skip_length")
				+",file_name="+req.getParameter("file_name")
				+",[---- "+req.getContextPath()+"-"+req.getRequestURL()+"]");
		
		String userUid = req.getParameter("user_uid");
		String file_name = req.getParameter("file_name");
		String file_md5 = req.getParameter("file_md5");
		
		// 合法性检查（tomcat9中，URLEncoder.encode(null) 将会发生空指针异常）
		if(file_name == null)
			file_name = "";
		
		// 要跳过的字节数（为了支持断点下载嘛）
		long skipLength = 0;
		String skipLength_s = req.getParameter("skip_length");
		
		BufferedOutputStream output= null;   
		BufferedInputStream input = null;   
		
		// 本次下载已成功读取并传输给客户端的字节数（本字段仅用于log中记录）
		long totoalRead = 0;
		
		try
		{
			try{
				skipLength = Long.parseLong(skipLength_s);
				
				// 修正不合法的参数值
				if(skipLength < 0)
				{
					LoggerFactory.getLog().debug("[HTTP][大文件下载] 参数skipLength_s"
							+ "解析完成，结果不合法："+skipLength+", 将被默认修正为0!");
					skipLength = 0;
				}
			} catch (Exception e){
				LoggerFactory.getLog().warn("[HTTP][大文件下载] 解析参数skipLength_s出错，skipLength_s="+skipLength_s);
			}
			
			if(CommonUtils.isStringEmpty(file_md5, true))
				throw new Exception("无效的参数，file_md5不可为空！");
			
			// 确保使用小写字母，与其它各种保持一致
			file_md5 = file_md5.toLowerCase();
			
//			String filePath = BigFileUploder.getFileSavePath(file_md5);
			String filePath = BigFileUploder.getCommonBigFileSavePath(file_md5);
			File f = new File(filePath);
			if(!f.exists())
				throw new Exception("要下载的文件"+filePath+"不存在于服务端磁盘上！");
			
			if(f.length() <= skipLength)
				throw new Exception("服务端文件小于预期，f.len="+f.length()
					+", skipLength="+skipLength+"！");
			
			LoggerFactory.getLog().debug("[HTTP][大文件下载] 马上要从"+filePath
					+"下载文件了，skipLength="+skipLength+",f.length()="+f.length());
		
			// http头中放入文件大小（content-length属性就是文件大小）
			res.setContentLength((int)(f.length() - skipLength)); 
			
			// http头中放入文件名（支持中文文件名）
			// ** 特别说明：RFC2183规定filename只能为US-ASCII码，而中文显然会乱码，但现代浏览器中
			// **          基本都已支持UTF-8编码，就像下面的代码中：使用“filename*=charset'lang'value“，
			// **          charset则是给浏览器指明以什么编码方式来还原中文文件名。
			// ** 参考资料：https://blog.csdn.net/weixin_33779515/article/details/90660069
			// **          https://blog.csdn.net/Jon_Smoke/article/details/53699400?fps=1&locationNum=5
			res.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"+ java.net.URLEncoder.encode(file_name, "UTF-8"));
//			res.setHeader("Content-disposition","attachment;filename=\""
//					//发给客户的数据先以GBK字符集转换成二进制再以ISO-8859-1的编码形式传回客户端
//					//客户端的传过来的数据同(因而服务端要进行逆过程--先获ISO-8859-1的二进制再编码成GBK)
////					+(FileDownload.isDoubleByteLang(file_name)?
////							FileDownload.getGBK2ISO_8859_1Str(file_name):file_name)+"\"");  
//					+file_name+"\"");  
			
//			int length=(new Long(f.length())).intValue();   
			int bufferSize=1024;   
			
			output = new BufferedOutputStream(res.getOutputStream());   
			input = new BufferedInputStream(new FileInputStream(f));  
			
			// 跳过用户之前已经下载过的字节（实现断点续传）
			if(skipLength > 0)
				input.skip(skipLength);
			
			int n = 0;
            byte[] buffer = new byte[bufferSize];
            while ((n = input.read(buffer)) != -1) 
            {
            	output.write(buffer,0,n); 
            	totoalRead += n;
            }
            
            res.flushBuffer();   
            
            LoggerFactory.getLog().debug("[HTTP][大文件下载]【下载成功完成】从"+filePath
					+"下载完成了(本次客户端共下载"+totoalRead+"字节)！(skipLength="+skipLength+",f.length()="+f.length()+")");
		}
		catch (Exception e)
		{
			String l = "[HTTP][大文件下载]【下载提前结束】任务没有继续(本次客户端共下载"+totoalRead+"字节)，原因是："
					+e.getMessage()+"(user_uid="+userUid
					+",file_name="+file_name+",file_md5="+file_md5+",skipLength="+skipLength+")";
			LoggerFactory.getLog().debug(l, e);
			
			try
			{
				res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
				res.getWriter().write(l);
			}
			catch (Exception e2)
			{
				//
			}
		}
		finally   
		{  
			try
			{
				if(input!= null)   
					input.close();   
				if(output!=null)   
					output.close();   
			}
			catch (Exception e2)
			{
//				LoggerFactory.getLog().warn(e2.getMessage(), e2);
			}
		}  
	}

}
